Writing a Scripting Engine
Writing demos (and synching) w/o a scripting engine is coding-your-brains-out as you might know. Wouldn't it be fun writing the effects methods and then dragging parts around in a track-based editor? To do so, we'll need two parts:
1. The editor
2. A managing method in our demo (the main thing)
All my code is written in C++, I hope you get the message, even if you're not used to C++. Some special constructs will be explained in more detail.
The editor
You can either write the editor for yourself (but it's gonna be a lotta work) or make yourself friends with the SquoQuo Demo Editor which I wrote for usage with my own scripting engine.
In this editor you can create, delete, rename, etc. (i.e. manage) so-called parts, which are mainly
rectangles placed on the screen. Each part is placed on a track and can be moved around in time.
Each part holds several method entries, i.e. you can assign predefined (in your engine) methods
to the part, sort them, etc.
Each method got parameters (as you all know ;) which can be set in different ways, too.
After this confusing introduction let me explain the structure doing a little example:
Let's say we want a pixel move around (neat style that is).
Simply write two methods, one that clears the screen and one that draws a pixel at a given
position w/ given color. Both methods are assigned to the part, i.e. every time the part is
executed both methods are called with the actual parameters.
Main thing about scripting is, we want the whole stuff synched to the sound. To do so, you can define ticks in the editor, parts can be snapped to ticks, etc.
Please see our editor page for further infos about the editor.
Now let's get into detail...
The file
The editor writes a project file, which holds all parts (sorted by start time) and for each part all method names and the parameters. The editor package contains the complete file format and an example implementation of the dispatcher, which reads the project file and does the managing stuff, like calling methods, calculating parameters, etc.
Main loop
Your main loop gets really simple now, something like this:
while (!stopped) {
startFrame(); // new frame begins, initialize timer routines
dispatcher->execute(getTimer());
flipScreens();
}
Now all the stuff is done in dispatcher->execute(), it checks which parts are active, sorts the active parts by track
number and executes them one by one.
To determine which part is active, simple comparisons must be done, the easy way is to check for every part if the start
is before the actual time and the end is after the actual time. This can get very slow, if you got many
parts.
So, what to do?
If we sort all parts by ascending start time, we must find the first active part. Following parts will start later (i.e.
later or at the same time), so simply check all following parts until a part is found with start>actual time. No other
parts can be active!
If we save the actually-found first active part number, we can use it in the next frame to quickly determine the first part again.
Check this piece of code to see what I mean:
void PartDispatcher::execute(float time)
{
int i, j, k;
int calledParts[16];
int dummy;
if (time>=totalTime)
setStopFlag(1);
// determine, which is the first actual part
i=actualFirstPart;
while ((parts[i]!=NULL)&&(parts[i]->position+parts[i]->length<=time))
i++;
actualFirstPart=i;
k=0; // determine, which parts must be executed
for (i=actualFirstPart; (parts[i]!=NULL)&&(parts[i]->position<=time); i++)
if (parts[i]->position+parts[i]->length>time) // part must be executed
calledParts[k++]=i;
// sort the parts depending on their track
for (i=k-1; i>0; i--)
for (j=0; j< i; j++)
if (parts[calledParts[j]]->track>parts[calledParts[j+1]]->track) {
dummy=calledParts[j];
calledParts[j]=calledParts[j+1];
calledParts[j+1]=dummy;
}
for (i=0; i<k; i++)
parts[calledParts[i]]->execute(time);
}
What does this code?
- First, if the actual time exceeds the total demo time, then stop it!
- Then, determine (dependent on actualFirstPart, which holds the first part of the last frame) the first active part, this is done by stepping through the (sorted, remember that) parts, starting with the "old" first part, until we find a part which is still active (i.e. start+length>time).
- For all following parts which could be active (i.e. start